/* 反射调用类的方法
我们可以通过反射来调用类的方法（本质上还是类的实例进行调用）只是利用反射机制实现了方法的调用
我们在src/com/test下创建一个新的Student类，普通情况下，想用使用这个类及成员变量或方法，必须import这个类，
但我们可以使用Class.forName反射,在不import的情况创建Student类的类对象，及调用方法
 */
package Class_Object;

import DataStructures.LinearList.ArrayList;

import java.lang.reflect.Method;  //用了反射，不用导入Student类，但又能用它
import java.util.Arrays;

public class ReflectMethod {
    public static void main(String[] args) throws ReflectiveOperationException {
        Class<?> clazz = Class.forName("com.test.Student");  //使用Class.forName反射获取类对象。因为不知道反射的对象是什么类型，所以用Class<?>
        Object instance = clazz.newInstance();  //创建出学生对象
        Method method = clazz.getMethod("test", String.class);  //getMethod()通过方法名和形参类型获取类中的方法，赋给Method对象。还有getMethods()，类比getConstructor理解
        method.invoke(instance,"大哥");  //method就代表类中的方法，通过method的invoke（调用的意思）,传入对象的实例和方法的参数
        System.out.println(method.invoke(instance, "大哥")); //invoke()返回值就是方法的返回值，因为这里是void，所以返回值为null
        // 通过getMethod()方法，我们可以获取到类中所有声明为public的方法，得到一个Method对象。我们可以通过Method的invoke()方法来调用已经获取到的方法，注意传入。

        /*
        我们发现，利用反射之后，在一个对象从构造到方法调用，没有任何一处需要引用到对象的实际类型，我们也没有导入Student类，整个过程都是反射在代替操作，
        使得整个过程被模糊了。过多的使用反射，会极大的降低后期维护性
         */

        //同构造方法一样，当出现非public方法时，我们可以通过反射来无视权限修饰符，获取非public方法调用，现在我们尝试调用private test2()方法
//        Class<?> clazz2 = Class.forName("com.test.Student");
//        Object instance2 = clazz2.newInstance();
        Method method2 = clazz.getDeclaredMethod("test2", String.class);  //使用getDeclaredMethod()方法获取声明的private方法，类比getDeclaredContructor()
        method2.setAccessible(true);  //修改方法访问权限
        method2.invoke(instance,"二哥");

        //Class存储了类的信息，Method和Constructor都和Class一样，他们存储了方法的信息，包括方法的形式参数列表、返回值、方法的名称等内容。我们可以直接通过Method对象获取这些信息。
        System.out.println(method2.getName());  //获取方法名
        System.out.println(method.getName());
        System.out.println(method.getReturnType()); //获取方法返回类型
        System.out.println(method.getParameterTypes());  //获取参数类型 [Ljava.lang.Class;@74a14482
        System.out.println(Arrays.toString(method.getParameterTypes()));  //获取参数类型 [class java.lang.String]

        Method method3 = clazz.getDeclaredMethod("test3", String.class, String.class);
        System.out.println(Arrays.toString(method3.getParameterTypes()));  //获取参数类型 [class java.lang.String, class java.lang.String]

        //当方法为可变参数时，该如何获取方法呢？实际上，之前已经提到过，可变参数实际上就是一个数组，因为我们可以直接使用数组的class对象表示。
        Method method4 = clazz.getDeclaredMethod("test4", String[].class);  //test()参数是可变参数，本质上是数组，这里传入的形参就写为数组.class
        System.out.println(method4.getName());
        System.out.println(method4.getReturnType()); //获取方法返回类型
        System.out.println(Arrays.toString(method4.getParameterTypes())); // [class [Ljava.lang.String;] [L表示是数组

        /*
        反射非常强大，尤其是刚才提到的越权访问，但是请一定谨慎使用，别人将某个方法设置为private一定有他的理由。
        如果是实在要用别人定位的private方法，你必须确保这样做是安全的的。在没有了解别人代码的整个过程的情况下就强行越权访问，可能会出现无法预知的错误。
         */
    }

}
